# -*- coding: utf-8 -*-

from threading import Thread
import stomp
import sys, time, logging
import types
import Queue
import inspect
import threading
import time
import socket


#TODO Move this to settings or to a environment option 
PROFILE_MEM=False

#used only to profile Memory usage
if PROFILE_MEM :
    try:  
        import guppy.heapy.heapyc
        from guppy import hpy
    except ImportError:
        logger.error("Couldn't import Guppy module.")
        PROFILE_MEM = False

    # time in seconds for memory reports to be logged
    interval = 60

try:
    import sysv_ipc
except ImportError: 
    logger.error("Couldn't import sysv_ipc module. It must me installed from http://semanchuk.com/philip/sysv_ipc/")
    exit(1)


from utils import parser


logger = logging.getLogger("nebpublisher.manager")
memlogger = logging.getLogger("nebpublisher.memprofiler")


class Manager(object):
    """ Responsible for initializing components:
         - OS message queue subscriber and parses (Subscriber)
         - Actimq sender via stomp (QueueProcessor)
    """    
    def __init__(self, settings, topics):
        logger.debug("Initiating Manager")
        
        self.settings = settings
        self.topics = topics
        
        #queue to send results to broker
        self.queue = Queue.Queue(settings.MAX_QUEUE_SIZE)   
        
        try:
          self.mq = sysv_ipc.MessageQueue(settings.OS_MQ_KEY)
        except sysv_ipc.ExistentialError:
          logger.error("Message queue does not exist for key %i . \
          Check if Nagios is using the same key and is running " % settings.OS_MQ_KEY )
          exit(1)
            
        #start execution      
        self.parser = parser.Parser(topics)  
        self.subscriber = Subscriber("subscriber", self.mq, settings, self.parser, self.queue).start();                   
          
        self.processor = QueueProcessor(self.queue, settings)

        #main thread will be processing
        self.processor.process()
            

class Subscriber(threading.Thread):
  def __init__ (self, name, mq, settings, parser, queue):
    threading.Thread.__init__(self)
    self.name = name
    self.mq = mq
    self.settings = settings
    self.parser = parser
    self.queue = queue
    self.header = settings.DESTINATION
    if PROFILE_MEM:
      memlogger.debug("Initiating memory profiler for subscriber")
      self.hp = hpy()
    
  def run (self):
    if PROFILE_MEM :
      time_ref = time.time()
      self.hp.setrelheap()
    
    while True:
      try:
        logger.debug("Waiting for a OS message:")
        
        #This reception blocks until some new message appears. Other option is to use flag IPC_NOWAIT
        message, type = self.mq.receive()
        message = str(message)
				
        if (message.find('\0') < 0):
          logger.warn("Message should end with '\\0' character")
          pass
        
        message, char, garbage = message.partition('\0')
        
        logger.debug("Message received. Type: %i Message: %s" %(type, str(message)))

        event = self.parser.parse(type, str(message))

        if(event != parser.NOT_IMPLEMENTED and event != parser.BAD_FORMAT):
          self.publish(event)

        if (PROFILE_MEM ):
          if (time.time() > time_ref + interval):
            memlogger.debug(self.hp.heap())
            time_ref = time.time()
      except sysv_ipc.PermissionsError, sysv_ipc.ExistentialError:
        logger.error("Message could not be received. Check if os queue exist and its permission")
        time.sleep(self.settings.OS_MQ_SLEEP)
        pass
      except sysv_ipc.InternalError:
        logger.error("A severe error ocurred in os message queue. Aborting..")
        exit(1)
      except Exception, e:
        logger.error('Unknown exception %s' % str(sys.exc_info()))
        exit(1)

    
  def publish(self, event):
    """ Commmon logic for publish event into queue """
    self.header.update({'timestamp': time.time()})
    self.header.update({'eventtype': event['eventtype'] })
    del event['eventtype']
    logger.debug("EVENT ---  %s " % str(event))
    if (self.queue.full()):
      self.queue.get_nowait()
      logger.warn("Queue between process is full. Dropping old messages")
    if (not self.queue.full()):
      self.queue.put((self.header, event))
      logger.debug("Message on queue")
    

class QueueProcessor(object):
    """ Responsible for processing objects in queue """
    def __init__(self, queue, settings):
        self.queue = queue
        self.connection = ConnectionAdapter(settings.BROKER, settings.CONN_SLEEP_DELAY) 
        self.settings = settings      
    
    def process(self):
        sent = True
        while True:
            try:    
                if sent:
                    #bloqueia se tiver vazio
                    header, body = self.queue.get(True)
                    logger.debug("HEADER %s" % str(header))
                    logger.debug("BODY %s" % str(body))
                    sent = False
                    logger.debug("New message taken from queue %s %s" % (str(header), str(body) ) )
                 
                self.connection.send(body, header, destination=header['destination'])               
                sent = True
                    
            except Queue.Empty:
              logger.debug(" --Empty queue-- ")
              continue
            
            except KeyboardInterrupt:
              logger.info("User interrupted. Shutting down.")
              exit(0)
              
            except socket.error:
              #try to reconnect
              logger.error("Socket error >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
              self.connection = ConnectionAdapter(self.settings.BROKER, self.settings.CONN_SLEEP_DELAY)
              
            except Exception, e:
              logger.error('Unknown exception %s' % str(sys.exc_info()))
              #TODO: Maybe retry
              continue


class ConnectionAdapter(object):
  """ Integrates with a the STOMP client API. """
  def __init__(self, broker, conn_sleep_delay):
    self.broker = broker
    self.conn_sleep_delay = conn_sleep_delay
    self.conn = self.__connect()
        
  def send(self, message, headers={}, **keyword_headers):
    try:
      self.conn.send(message, headers, **keyword_headers)
            
    except stomp.NotConnectedException :
      logger.error("Lost connection with '%s'." % (self.broker)) 
      time.sleep(self.conn_sleep_delay)
      # A propria api do stomp tenta se reconectar automaticamente, contudo apenas na parte TCP
      # É necessario verificar se o TCP está conectado para entao conectar o Stomp.
      try:
        if self.conn.is_connected(): 
          self.conn.connect()
      except:
        logger.debug("After wait... try again")
        
  def __connect(self):
    """ Attempts to connect to broker """
    try:
      connection = stomp.Connection(self.broker, '', '')
      connection.add_listener(ErrorListener(connection))
      connection.start()
      connection.connect()
    except Exception:
      if type(sys.exc_info()[1]) == types.TupleType:
        exc = sys.exc_info()[1][1]
      else:
        exc = sys.exc_info()[1]
        logger.error('Unexpected error %s.' % (exc))
        return connection
    else:
      return connection


class ErrorListener(stomp.ConnectionListener):
  def __init__(self, connection):
    self.connection = connection
    
  def on_error(self, headers, message):
    logger.error('received an error %s' % message)
    if self.connection.is_connected:
      # This necessary because of an activemq bug - https://issues.apache.org/activemq/browse/AMQ-1376
      logger.error('TCP is connect but has some errors')
      self.connection.stop()
